package com.zluyuer.ermu;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;

public class ErmuServer {
    
    private static Logger log = Logger.getLogger(ErmuServer.class);

    private ServerSocket serverSocket;
    private ExecutorService executorService;
    private Map<String, Thread> threads;
    private int threadPoolSize = 4;
    
    private boolean initialized = false;
    private boolean running = true;
    
    private String bindIp = "0.0.0.0";
    private int bindPort = 9999;
    
    
    public ErmuServer() {}
    
    public ErmuServer(int bindPort) {
        this.bindPort = bindPort;
    }
    
    public ErmuServer(String bindIp, int bindPort) {
        this.bindIp = bindIp;
        this.bindPort = bindPort;
    }
    
    public void startup() throws IOException {
        if (initialized) {
            log.warn("ErmuServer already started");
            return ;
        }
        
        log.info("Starting ErmuServer, bind to " + bindIp + ":" + bindPort + "...");
        
        serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(bindIp, bindPort));
        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*threadPoolSize);
        threads = new HashMap<String, Thread>();
        initialized = true;
        
        final ErmuServer server = this;
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (running) {
                        Socket socket = serverSocket.accept();
                        socket.setTcpNoDelay(true);
                        
                        log.info("Receiving request from " + socket.getRemoteSocketAddress());
                        
                        ErmuServerThread thread = new ErmuServerThread(socket, server);
                        thread.setName("ErmuServerThread[" + threads.size() + "]");
                        registerThread(thread.getName(), thread);
                        executorService.execute(thread);
                        
                        log.info(thread.getName() + " created successfully");
                    }
                } catch (IOException e) {
                    String msg = e.getMessage();
                    if (!msg.equals("socket closed")) {
                        log.error("ErmuServer error: " + msg);
                    }
                }
            }
        }).start();
        
        log.info("ErmuServer started successfully");
    }
    
    public void close() throws IOException {
        if (serverSocket != null) {
            log.info("Closing ErmuServer...");
            
            executorService.shutdown();
            serverSocket.close();
            serverSocket = null;
            initialized = false;
            running = false;
            
            log.info("ErmuServer closed successfully");
        }
    }
    
    public void registerThread(String name, Thread thread) {
        threads.put(name, thread);
    }
    
    public void unregisterThread(String name) {
        if (threads.containsKey(name)) {
            threads.remove(threads.get(name));
        }
    }
    
    public void setThreadPoolSize(int threadPoolSize) {
        this.threadPoolSize = threadPoolSize;
    }
    
    public void setBindIp(String bindIp) {
        this.bindIp = bindIp;
    }
    
    public void setBindPort(int bindPort) {
        this.bindPort = bindPort;
    }
}
